package robot;

import change.Revision;
import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.SgmlPage;
import com.gargoylesoftware.htmlunit.html.DomNode;
import commonutils.MyCollectionUtils;
import commonutils.MyUrlUtils;
import commonutils.MyXPathUtils;
import downloader.HtmlUnitPageProvider;
import downloader.PageProvider;
import page.Lang;
import page.Link;
import page.WikiPage;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.Logger;

/**
 * Created by IntelliJ IDEA.
 * User: Evgeniy Krasko
 * Date: 20.11.11
 * Time: 20:48
 */
public class Robot {
    private final PageProvider pageProvider = HtmlUnitPageProvider.getInstance();
    private final ExecutorService pool = Executors.newFixedThreadPool(CONNECTIONS_COUNT);

    private static final Logger logger = Logger.getLogger(Robot.class.getName());

    private static final int MAX_TITLES_IN_SINGLE_QUERY = 50;
    private static final int LINKS_LIMIT = 500;
    private static final int CHANGES_LIMIT = 500;
    private static final String CHANGES_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
    private static final DateFormat CHANGES_DATE_FORMATTER = new SimpleDateFormat(CHANGES_DATE_FORMAT);

    private static final Set<String> ACCEPTED_TYPES = new HashSet<String>();
    private static final int CONNECTIONS_COUNT = 10;

    static {
        ACCEPTED_TYPES.add("new");
        ACCEPTED_TYPES.add("edit");
    }

    public List<Link> getInterlinks(List<WikiPage> pages) {
        final Map<Lang, List<WikiPage>> pagesByLangs = splitPagesByLangs(pages);
        final Set<Link> links = new HashSet<Link>();
        for (Lang lang : pagesByLangs.keySet()) {
            for (final List<WikiPage> currentQueryList :
                    MyCollectionUtils.splitList(pagesByLangs.get(lang), MAX_TITLES_IN_SINGLE_QUERY)) {
                addInterlinks(links, currentQueryList);
            }
        }
        return Arrays.asList(links.toArray(new Link[links.size()]));
    }

    public List<Revision> getChangedPages(Lang lang, Date from, Date to) {
        final Map<WikiPage, Revision> revisionMap = new HashMap<WikiPage, Revision>();
        final String url = MyUrlUtils.getApiUrl(lang);
        final Map<String, String> params = new HashMap<String, String>();
        params.put("action", "query");
        params.put("list", "recentchanges");
        params.put("format", "xml");
        params.put("rctype", "new|edit");
        params.put("rcnamespace", "0");
        params.put("rcshow", "!bot|!minor|!anon");
        params.put("rclimit", String.valueOf(CHANGES_LIMIT));
        params.put("rcstart", CHANGES_DATE_FORMATTER.format(to));
        params.put("rcend", CHANGES_DATE_FORMATTER.format(from));
        boolean queryContinue = true;
        while (queryContinue) {
            final SgmlPage queryResult = pageProvider.getPage(url, params, HttpMethod.GET);
            final DomNode changesContinue = MyXPathUtils.getSingleDomNode(queryResult, "//recentchanges/@rcstart");
            if (changesContinue == null) {
                queryContinue = false;
            } else {
                params.put("rcstart", changesContinue.getNodeValue());
            }
            final List changes = queryResult.getByXPath("//rc");
            for (final Object changeObj : changes) {
                if (changeObj instanceof DomNode) {
                    addChange((DomNode) changeObj, revisionMap, lang);
                }
            }
        }
        final List<Revision> result = new ArrayList<Revision>();
        result.addAll(revisionMap.values());
        return result;
    }


    private void addChange(DomNode changeNode, Map<WikiPage, Revision> result, Lang lang) {
        final String title = MyXPathUtils.getSingleDomNode(changeNode, "./@title").getNodeValue();
        try {
            final Date changeDate = CHANGES_DATE_FORMATTER.parse(MyXPathUtils.getSingleDomNode(changeNode, "./@timestamp").getNodeValue());
            final long revId = Long.valueOf(MyXPathUtils.getSingleDomNode(changeNode, "./@revid").getNodeValue());
            final String type = MyXPathUtils.getSingleDomNode(changeNode, "./@type").getNodeValue();
            if (!ACCEPTED_TYPES.contains(type)) {
                return;
            }
            final WikiPage wikiPage = new WikiPage(lang, title);
            final Revision change = new Revision(revId, wikiPage, changeDate);
            if (!result.containsKey(wikiPage) || change.getRevId() > result.get(wikiPage).getRevId()) {
                result.put(wikiPage, change);  //put if change is newer, or first change in this series
            }
        } catch (ParseException e) {
            //Do nothing, Parse error
        }
    }


    public Map<Revision, SgmlPage> getHtmlPages(List<Revision> revisions) {
        final Map<Revision, SgmlPage> result = new ConcurrentHashMap<Revision, SgmlPage>();
        final List<Future> futures = new ArrayList<Future>();
        for (final Revision change : revisions) {
            final String url = MyUrlUtils.getIndexPhpUrl(change.getWikiPage().getLang());
            final Map<String, String> params = new HashMap<String, String>();
            params.put("title", change.getWikiPage().getTitle());
            params.put("oldid", String.valueOf(change.getRevId()));
            futures.add(pool.submit(new Runnable() {
                public void run() {
                    final SgmlPage page = HtmlUnitPageProvider.getInstance().getPage(url, params, HttpMethod.GET);
                    result.put(change, page);
                }
            }));
        }
        for (final Future future : futures) {
            try {
                future.get();
            } catch (Exception e) {
                //do nothing
            }
        }
        return result;
    }

    public Map<Revision, String> getWikiSources(List<Revision> changes) {
        throw new NotImplementedException();
    }

    private void addInterlinks(Set<Link> links, List<WikiPage> currentQueryList) {
        final Lang pageLang = currentQueryList.get(0).getLang();
        final String url = MyUrlUtils.getApiUrl(pageLang);
        final StringBuilder titles = new StringBuilder();
        boolean first = true;
        for (WikiPage page : currentQueryList) {
            if (first) {
                first = false;
            } else {
                titles.append("|");
            }
            titles.append(page.getTitle());
        }
        final Map<String, String> params = new HashMap<String, String>();
        params.put("titles", titles.toString());
        params.put("action", "query");
        params.put("prop", "langlinks");
        params.put("format", "xml");
        params.put("lllimit", String.valueOf(LINKS_LIMIT));
        boolean queryContinue = true;
        while (queryContinue) {
            SgmlPage queryResult = pageProvider.getPage(url, params, HttpMethod.POST);
            DomNode llContinue = MyXPathUtils.getSingleDomNode(queryResult, "//langlinks/@llcontinue");
            if (llContinue == null) {
                queryContinue = false;
            } else {
                params.put("llcontinue", llContinue.getNodeValue());
            }
            for (WikiPage wikiPage : currentQueryList) {
                addLinksFromSinglePage(links, queryResult, wikiPage);
            }
        }
    }

    private void addLinksFromSinglePage(Set<Link> links, SgmlPage queryResult, WikiPage wikiPage) {
        final List pageInfo = queryResult.getByXPath("//page[translate(@title,'\"','')=\"" + wikiPage.getTitle().replace("\"", "") + "\"]//ll");
        for (final Object langLinkObj : pageInfo) {
            if (langLinkObj instanceof DomNode) {
                DomNode langLink = (DomNode) langLinkObj;
                String domainPrefix = MyXPathUtils.getSingleDomNode(langLink, "./@lang").getNodeValue();
                String title = MyXPathUtils.getSingleDomNode(langLink, "./text()").getNodeValue();
                Lang lang = Lang.getLang(domainPrefix);
                if (lang != null) {
                    links.add(new Link(wikiPage, new WikiPage(lang, title)));
                }
            }
        }
    }

    private Map<Lang, List<WikiPage>> splitPagesByLangs(List<WikiPage> pages) {
        final Map<Lang, List<WikiPage>> result = new HashMap<Lang, List<WikiPage>>();
        for (final WikiPage page : pages) {
            if (!result.containsKey(page.getLang())) {
                result.put(page.getLang(), new ArrayList<WikiPage>());
            }
            result.get(page.getLang()).add(page);
        }
        return result;
    }


}
